import { Matrix3 } from '../../math/Matrix3.js';
import { Plane } from '../../math/Plane.js';

function WebGLClipping(properties) {
  const scope = this;

  let globalState = null;
  let numGlobalPlanes = 0;
  let localClippingEnabled = false;
  let renderingShadows = false;

  const plane = new Plane();
  const viewNormalMatrix = new Matrix3();

  const uniform = { value: null, needsUpdate: false };

  this.uniform = uniform;
  this.numPlanes = 0;
  this.numIntersection = 0;

  this.init = function (planes, enableLocalClipping, camera) {
    const enabled =
      planes.length !== 0 ||
      enableLocalClipping ||
      // enable state of previous frame - the clipping code has to
      // run another frame in order to reset the state:
      numGlobalPlanes !== 0 ||
      localClippingEnabled;

    localClippingEnabled = enableLocalClipping;

    globalState = projectPlanes(planes, camera, 0);
    numGlobalPlanes = planes.length;

    return enabled;
  };

  this.beginShadows = function () {
    renderingShadows = true;
    projectPlanes(null);
  };

  this.endShadows = function () {
    renderingShadows = false;
    resetGlobalState();
  };

  this.setState = function (material, camera, useCache) {
    const planes = material.clippingPlanes;
    const clipIntersection = material.clipIntersection;
    const clipShadows = material.clipShadows;

    const materialProperties = properties.get(material);

    if (!localClippingEnabled || planes === null || planes.length === 0 || (renderingShadows && !clipShadows)) {
      // there's no local clipping

      if (renderingShadows) {
        // there's no global clipping

        projectPlanes(null);
      } else {
        resetGlobalState();
      }
    } else {
      const nGlobal = renderingShadows ? 0 : numGlobalPlanes;
      const lGlobal = nGlobal * 4;

      let dstArray = materialProperties.clippingState || null;

      uniform.value = dstArray; // ensure unique state

      dstArray = projectPlanes(planes, camera, lGlobal, useCache);

      for (let i = 0; i !== lGlobal; ++i) {
        dstArray[i] = globalState[i];
      }

      materialProperties.clippingState = dstArray;
      this.numIntersection = clipIntersection ? this.numPlanes : 0;
      this.numPlanes += nGlobal;
    }
  };

  function resetGlobalState() {
    if (uniform.value !== globalState) {
      uniform.value = globalState;
      uniform.needsUpdate = numGlobalPlanes > 0;
    }

    scope.numPlanes = numGlobalPlanes;
    scope.numIntersection = 0;
  }

  function projectPlanes(planes, camera, dstOffset, skipTransform) {
    const nPlanes = planes !== null ? planes.length : 0;
    let dstArray = null;

    if (nPlanes !== 0) {
      dstArray = uniform.value;

      if (skipTransform !== true || dstArray === null) {
        const flatSize = dstOffset + nPlanes * 4;
        const viewMatrix = camera.matrixWorldInverse;

        viewNormalMatrix.getNormalMatrix(viewMatrix);

        if (dstArray === null || dstArray.length < flatSize) {
          dstArray = new Float32Array(flatSize);
        }

        for (let i = 0, i4 = dstOffset; i !== nPlanes; ++i, i4 += 4) {
          plane.copy(planes[i]).applyMatrix4(viewMatrix, viewNormalMatrix);

          plane.normal.toArray(dstArray, i4);
          dstArray[i4 + 3] = plane.constant;
        }
      }

      uniform.value = dstArray;
      uniform.needsUpdate = true;
    }

    scope.numPlanes = nPlanes;
    scope.numIntersection = 0;

    return dstArray;
  }
}

export { WebGLClipping };
